package cn.xenosp.server.config.mybatis;

import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.lang.reflect.Method;
import java.sql.Date;
import java.text.DateFormat;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;

/**
 * 项目名： service-web
 * 包路径： cn.xenosp.server.config.mybatis
 * 作者：   TongHui
 * 创建时间: 2024-09-02 17:39
 * 描述: sql 打印
 * 版本: 1.0
 */
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Slf4j
public class MybatisPlusAllSqlLog implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 统计sql执行耗时
        long startTime = System.currentTimeMillis();
        Object proceed = invocation.proceed();
        long endTime = System.currentTimeMillis();

        String printSql = null;
        try {
            printSql = generateSql(invocation);
        } catch (Exception exception) {
            log.error("获取sql异常", exception);
        } finally {
            long costTime = endTime - startTime;
            int size = 0;
            if (proceed instanceof List){
                size = ((List)proceed).size();
            }else if (proceed instanceof Integer){
                size = (int) proceed;
            }
            if (costTime > 6000){
                System.err.println("\n执行SQL耗时："+costTime+"ms \n执行SQL："+printSql+" \n执行结果："+size);
            }else {
                log.info("\n执行SQL耗时：{}ms \n执行SQL：{} \n执行结果：{}", costTime, printSql,size);
            }
        }
        return proceed;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    private static String generateSql(Invocation invocation) {

        MappedStatement statement = (MappedStatement) invocation.getArgs()[0];
        String sqlId = statement.getId();
        Object parameter = null;
        Page page = null;
        if (invocation.getArgs().length > 1) {
            parameter = invocation.getArgs()[1];
            if (parameter instanceof Map ) {
                if (((Map) parameter).containsKey("page")){
                    page = (Page) ((Map) parameter).get("page");
                }
            }
        }
        Configuration configuration = statement.getConfiguration();
        BoundSql boundSql = statement.getBoundSql(parameter);

        // 获取参数对象
        Object parameterObject = boundSql.getParameterObject();
        // 获取参数映射
        List<ParameterMapping> params = boundSql.getParameterMappings();
        // 获取到执行的SQL
        String sql = boundSql.getSql();
        // SQL中多个空格使用一个空格代替
        sql = sql.replaceAll("[\\s]+", " ");
        if (sqlId.endsWith("_mpCount")){
            String countSql = "SELECT COUNT(*) " + sql.substring(sql.toLowerCase(Locale.ROOT).indexOf("from"));
            // 处理查询总数sql的参数替换等逻辑（复用下面的参数处理逻辑）
            countSql = handleSqlParameterReplacement(configuration, countSql, parameterObject, params,boundSql);
            return countSql;
        }

        sql = handleSqlParameterReplacement(configuration, sql, parameterObject, params,boundSql);
        if (page != null){
            // 在这里可以获取到Page对象中的分页属性，比如当前页码、每页显示数量等
            long current = page.getCurrent()-1;
            long size = page.getSize();
            sql += " LIMIT "+current+","+size;
        }
        return sql;
    }
    private static String handleSqlParameterReplacement(Configuration configuration, String sql,
                                                        Object parameterObject, List<ParameterMapping> params,BoundSql boundSql) {
        if (!ObjectUtils.isEmpty(params) && !ObjectUtils.isEmpty(parameterObject)) {
            // TypeHandlerRegistry 是 MyBatis 用来管理 TypeHandler 的注册器 TypeHandler 用于在 Java 类型和 JDBC 类型之间进行转换
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            // 如果参数对象的类型有对应的 TypeHandler，则使用 TypeHandler 进行处理
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(parameterObject)));
            } else {
                // 否则，逐个处理参数映射
                for (ParameterMapping param : params) {
                    // 获取参数的属性名
                    String propertyName = param.getProperty();
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    // 检查对象中是否存在该属性的 getter 方法，如果存在就取出来进行替换
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
                        // 检查 BoundSql 对象中是否存在附加参数
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
                    } else {
                        // SQL匹配不上，带上“缺失”方便找问题
                        sql = sql.replaceFirst("\\?", "缺失");
                    }
                }
            }

        }
        return sql;
    }
    private static String getParameterValue(Object object) {
        String value = "";
        if (object instanceof String) {
            value = "'" + object + "'";
        } else if (object instanceof Date) {
            DateFormat format = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + format.format((Date) object) + "'";
        } else if (!ObjectUtils.isEmpty(object)) {
            value = object.toString();
        }
        return value;
    }
}
